#!/usr/bin/python

from Tix import *
from Meter import Meter
import tkFileDialog, tkSimpleDialog, tkMessageBox
from ScrolledText import ScrolledText
import tkFont

from yapget import *
import Queue, os, time, urlparse, threading, ConfigParser, traceback

gui_version = "1.2"

class PersistentConfig:
	def __init__(self):
		self.parser = ConfigParser.SafeConfigParser()
		self.conf_file = find_file('yapgui.conf')
		self.main_sec = 'main'
		self._defaults = [
			['user', ''],
			['password', ''],
			['retries', '3'],
			['overwrite', 'False'],
			['timeout', '120'],
			['dl_dir', '.'],
			['cookie_dir', '.'],
			['no_proxy', 'False'],
			['explicit_proxy', ''],
		]

	def _validate_config(self):
		modified = False
		if not self.parser.has_section(self.main_sec):
			self.parser.add_section(self.main_sec)
			modified = True
		
		for vals in self._defaults:
			if not self.parser.has_option(self.main_sec, vals[0]):
				self.set(*vals)
				modified = True
		
		if modified:
			self.write()

	def set(self, setting, value):
		if type(value) == type(u''):
			value = value.encode('utf-8')
		self.parser.set(self.main_sec, setting, value)

	def get(self, setting):
		value = self.parser.get(self.main_sec, setting)
		return value.decode('utf-8')

	def getboolean(self, setting):
		return self.parser.getboolean(self.main_sec, setting)

	def read(self):
		if os.path.isfile(self.conf_file):
			self.parser.read(self.conf_file)
			self._validate_config()
		else:
			self.parser.add_section(self.main_sec)
			for vals in self._defaults:
				self.set(*vals)
			self.write()

	def write(self):
		fd = file(self.conf_file, 'w')
		self.parser.write(fd)
		fd.close()

	def __getitem__(self, value):
		return self.get(value)

	def __setitem__(self, value, setting):
		return self.set(value, setting)

class StatusReportWin:
	"""Print error messages to the window"""
	def __init__(self, parent):
		self.parent = parent

	def report_start(self, url):
		self.parent.guiqueue.put((self.parent.gui.statuswin.insert_text, ['Start downloading %s\n' % url, 'info'], {}))

	def report_finish(self, fname):
		self.parent.guiqueue.put((self.parent.gui.curr_row.mark_completed, [], {}))
		self.parent.guiqueue.put((self.parent.gui.save_status, [], {}))
		self.parent.guiqueue.put((self.parent.gui.statuswin.insert_text, ['Saved %s\n' % fname, 'info'], {}))

	def report_progress(self, percent_str, counter, total_len_str, speed_str, eta_str):
		self.parent.guiqueue.put((self.parent.gui.curr_row.set_progress, [percent_str, speed_str, eta_str], {}))

	def report_completion(self, total_len_str, speed_str, time_str):
		self.parent.guiqueue.put((self.parent.gui.curr_row.set_end_result, [speed_str, time_str], {}))
		self.parent.guiqueue.put((self.parent.gui.statuswin.insert_text, ['Got %s at %8s/s during %s\n' % (total_len_str, speed_str, time_str)], {}))

	def report_error(self, str):
		self.parent.guiqueue.put((self.parent.gui.statuswin.insert_text, ['Error: %s\n' % str, 'error'], {}))

	def report_warning(self, str):
		self.parent.guiqueue.put((self.parent.gui.statuswin.insert_text, ['Warning: %s\n' % str, 'warning'], {}))

class SelectableText(Text):
	"""Widget for text that can be selected"""
	def __init__(self, master, text_data, **cnf):
		cnf['height'] = 1
		cnf['width'] = 1
		cnf['cursor'] = ''
		cnf['relief'] = FLAT
		cnf['borderwidth'] = 0
		cnf['padx'] = 0
		cnf['pady'] = 0
		Text.__init__(self, master, **cnf)
		self.insert(END, text_data)
		self.config(state=DISABLED)

	def focus_set(self):
		pass

	def focus_force(self):
		pass

	def focus(self):
		pass

class URLRow(Frame):
	def __init__(self, master, url, **cnf):
		cnf['bd'] = 1
		cnf['relief'] = SOLID
		Frame.__init__(self, master, **cnf)
		spacing = 1

		self.url = url
		self.root = master
		self.var = BooleanVar()
		self.checkbox = Checkbutton(self, variable=self.var)

		self.checkbox.select()
		label = SelectableText(self, url)
		self.meter = Meter(self)
		self.meter.set(0, ' ')
		self.completed = False

		self.eta = Label(self, width=7)
		self.speed = Label(self, width=8, relief=SUNKEN)

		self.checkbox.pack(side=LEFT, padx=spacing)
		label.pack(side=LEFT, fill=X, expand=1, padx=spacing)
		self.eta.pack(side=RIGHT, padx=spacing)
		self.speed.pack(side=RIGHT, padx=spacing)
		self.meter.pack(side=RIGHT, padx=spacing)
		label.focus_set()

	def get_url(self):
		return self.url

	def set_progress(self, percent, speed, eta):
		if percent.startswith('-'):
			self.meter.set(0)
		else:
			self.meter.set(float(percent))
		self.eta.config(text=eta)
		self.speed.config(text=speed + '/s')

	def set_end_result(self, speed, total_time):
		self.eta.config(text=total_time)
		self.speed.config(text=speed + '/s')

	def mark_completed(self):
		self.completed = True
		self.meter.set(0, 'Completed')
		self.checkbox.deselect()
		self.checkbox.config(state=DISABLED)

class StatusBar(Frame):
	def __init__(self, master):
		Frame.__init__(self, master)
		self.label = Label(self, bd=1, relief=SUNKEN, anchor=W)
		self.label.pack(fill=X)

	def set(self, format, *args):
		self.label.config(text=format % args)
		self.label.update_idletasks()

	def clear(self):
		self.label.config(text='')
		self.label.update_idletasks()

class GridTable(ScrolledWindow):
	def __init__(self, master, **cnf):
		cnf['scrollbar'] = 'auto'
		ScrolledWindow.__init__(self, master, **cnf)
		self.rows = []

	def add_row(self, url):
		row = URLRow(self.window, url)
		self.rows.append(row)
		row.pack(fill=BOTH, expand=0, anchor=NW)

	def clear(self):
		for row in self.rows:
			row.pack_forget()
			del row
		self.rows = []

	def remove_row(self, row):
		self.rows.remove(row)
		row.pack_forget()
		del row

class ButtonPane(Frame):
	def __init__(self, master, **cnf):
		cnf['bd'] = 3
		cnf['relief'] = RAISED
		Frame.__init__(self, master, **cnf)

		self.root = master
		self.start_btn = Button(self, text='Start', width=14, command=self.on_start)
		self.stop_btn = Button(self, text='Stop', width=14, command=self.on_stop, state=DISABLED)
		self.select_btn = Button(self, text='Select all', width=14, command=self.on_select)
		self.deselect_btn = Button(self, text='Deselect all', width=14, command=self.on_deselect)
		self.clear_btn = Button(self, text='Clear', width=14, command=self.on_clear)
		#self.clear_comp_btn = Button(self, text='Clear completed', width=14, command=self.on_clear_comp)

		self.start_btn.grid(row=0, column=0, sticky=W)
		self.stop_btn.grid(row=0, column=1, sticky=W)
		self.select_btn.grid(row=0, column=2, sticky=W)
		self.deselect_btn.grid(row=0, column=3, sticky=W)
		self.clear_btn.grid(row=0, column=4, sticky=W)
		#self.clear_comp_btn.grid(row=0, column=5, sticky=W)

	def set_state_getting(self):
		self.master.unbind_all('<Control-o>')
		self.master.unbind_all('<Control-O>')

		self.master.unbind_all('<Control-s>')
		self.master.unbind_all('<Control-S>')

		self.root.currently_getting = 0
		self.root.filemenu.entryconfig(0, state=DISABLED)
		self.root.filemenu.entryconfig(2, state=DISABLED)
		self.root.filemenu.entryconfig(5, state=DISABLED)
		self.stop_btn.configure(state=NORMAL)
		self.start_btn.configure(state=DISABLED)
		self.clear_btn.configure(state=DISABLED)
		#self.clear_comp_btn.configure(state=DISABLED)
		self.select_btn.configure(state=DISABLED)
		self.deselect_btn.configure(state=DISABLED)
		for row in self.root.table.rows:
			if not row.completed:
				row.checkbox.config(state=DISABLED)

	def set_state_idle(self):
		self.master.bind_all('<Control-o>', lambda event: self.master.file_open_file())
		self.master.bind_all('<Control-O>', lambda event: self.master.file_open_file())

		self.master.bind_all('<Control-s>', lambda event: self.master.file_open_url_src())
		self.master.bind_all('<Control-S>', lambda event: self.master.file_open_url_src())

		self.start_btn.configure(state=NORMAL)
		self.clear_btn.configure(state=NORMAL)
		#self.clear_comp_btn.configure(state=NORMAL)
		self.select_btn.configure(state=NORMAL)
		self.deselect_btn.configure(state=NORMAL)
		self.stop_btn.configure(state=DISABLED)
		self.root.currently_getting = 0
		self.root.filemenu.entryconfig(0, state=NORMAL)
		self.root.filemenu.entryconfig(2, state=NORMAL)
		self.root.filemenu.entryconfig(5, state=NORMAL)
		for row in self.root.table.rows:
			if not row.completed:
				row.checkbox.config(state=NORMAL)

	def on_start(self):
		self.set_state_getting()
		for row in self.root.table.rows:
			if row.var.get():
				self.root.currently_getting += 1
				self.root.queue.put(('url', (row.get_url(), row)))
		if self.root.currently_getting <= 0:
			self.set_state_idle()

	def on_stop(self):
		while True:
			try: self.root.queue.get_nowait()
			except: break
		config.stop_getting = True
		self.set_state_idle()

	def on_select(self):
		for row in self.root.table.rows:
			if not row.completed:
				row.checkbox.select()

	def on_deselect(self):
		for row in self.root.table.rows:
			if not row.completed:
				row.checkbox.deselect()

	def on_clear(self):
		self.root.table.clear()
		self.root.save_status()

	def on_clear_comp(self):
		for row in self.root.table.rows:
			if row.completed:
				self.root.table.remove_row(row)
		self.root.save_status()

class PropertiesDialog(tkSimpleDialog.Dialog):
	def body(self, master):
		self.title('Properties')
		self.proxy_var = BooleanVar()
		self.proxy = Checkbutton(master, text="Don't use proxy", variable=self.proxy_var)

		if config.no_proxy:
			self.proxy.select()
		else:
			self.proxy.deselect()

		self.overwrite_var = BooleanVar()
		self.overwrite = Checkbutton(master, text='Overwrite existing', variable=self.overwrite_var)

		if config.overwrite_existing:
			self.overwrite.select()
		else:
			self.overwrite.deselect()

		self.retries_var = IntVar()
		self.retries_var.set(config.retries)
		self.retries = Control(master, min=1, max=999, integer=1, variable=self.retries_var, label='Maximum number of retries')
		self.tmo_var = IntVar()
		self.tmo_var.set(int(config.tmo))
		self.tmo = Control(master, min=1, max=360, integer=1, variable=self.tmo_var, label='Timeout in seconds')

		self.user_entry = LabelEntry(master, label='Rapidshare login', options='entry.width 25 label.width 20 label.anchor w')
		self.user_entry.entry.insert(0, config.rs_user)
		self.pass_entry = LabelEntry(master, label='Rapidshare password', options='entry.width 25 label.width 20 label.anchor w entry.show *')
		self.pass_entry.entry.insert(0, config.rs_passwd)

		self.dl_entry = LabelEntry(master, label='Download directory', options='entry.width 64 label.width 20 label.anchor w')
		self.dl_entry.entry.insert(0, os.path.abspath(config.dl_dir))
		dl_dir_btn = Button(master, text='...', command=self.browse_dl_dir)
		self.cookie_entry = LabelEntry(master, label='Cookie directory', options='entry.width 64 label.width 20 label.anchor w')
		self.cookie_entry.entry.insert(0, os.path.abspath(config.cookie_dir))
		cookie_dir_btn = Button(master, text='...', command=self.browse_cookie_dir)

		self.proxy_entry = LabelEntry(master, label='Explicit proxy', options='entry.width 40 label.width 20 label.anchor w')
		if config.explicit_proxy:
			self.proxy_entry.entry.insert(0, config.explicit_proxy)

		self.exit = -1

		self.dl_entry.grid(row=0, sticky=W, columnspan=2)
		dl_dir_btn.grid(row=0, column=2, sticky=W)
		self.cookie_entry.grid(row=1, sticky=W, columnspan=2)
		cookie_dir_btn.grid(row=1, column=2, sticky=W)

		self.retries.grid(row=2, column=0, sticky=W)
		self.tmo.grid(row=2, column=1, sticky=W)
		
		self.user_entry.grid(row=3, sticky=W)
		self.pass_entry.grid(row=4, sticky=W)
		self.proxy.grid(row=3, column=1, sticky=W)
		self.overwrite.grid(row=4, column=1, sticky=W)
		self.proxy_entry.grid(row=5, sticky=W, columnspan=2)
		return self.dl_entry

	def browse_dl_dir(self):
		f = tkFileDialog.askdirectory(parent=self, initialdir=config.dl_dir, title='Choose download directory')
		if f:
			self.dl_entry.entry.delete(0, END)
			self.dl_entry.entry.insert(0, os.path.abspath(f))

	def browse_cookie_dir(self):
		f = tkFileDialog.askdirectory(parent=self, initialdir=config.cookie_dir, title='Choose cookie directory')
		if f:
			self.cookie_entry.entry.delete(0, END)
			self.cookie_entry.entry.insert(0, os.path.abspath(f))

	def apply(self):
		config.set_dl_dir(self.dl_entry.entry.get())
		config.rs_user = self.user_entry.entry.get()
		config.rs_passwd = self.pass_entry.entry.get()
		config.set_tmo(self.tmo_var.get())
		config.set_retries(self.retries_var.get())
		config.overwrite_existing = self.overwrite_var.get()
		proxy = self.proxy_entry.entry.get()
		config.init_comm(self.cookie_entry.entry.get(), self.proxy_var.get(), proxy)

		conf = self.master.conf
		conf['dl_dir'] = config.dl_dir
		conf['user'] = config.rs_user
		conf['password'] = config.rs_passwd
		conf['timeout'] = str(config.tmo)
		conf['no_proxy'] = str(config.no_proxy)
		conf['overwrite'] = str(config.overwrite_existing)
		conf['retries'] = str(config.retries)
		conf['cookie_dir'] = config.cookie_dir
		conf['explicit_proxy'] = config.explicit_proxy
		conf.write()

	def validate(self):
		proxy = self.proxy_entry.entry.get()
		if proxy and not is_valid_proxy_url(proxy):
			tkMessageBox.showwarning(
 				"Bad input",
				"Invalid explicit proxy specified. Must be in http://username:password@host:port format")
			return 0

		return 1

class App:
	"""
	Launch the main part of the GUI and the worker thread. periodicCall and
	endApplication could reside in the GUI part, but putting them here
	means that you have all the thread controls in a single place.
	"""
	def __init__(self):
		"""
		Start the GUI and the asynchronous threads. We are in the main
		(original) thread of the application, which will later be used by
		the GUI. We spawn a new thread for the worker.
		"""
		self.guiqueue = Queue.Queue()

		self.gui = AppGui(self.guiqueue, self.endApplication)

		# Set up the thread to do asynchronous I/O
		self.running = True
		self.thread1 = threading.Thread(target=self.workerThread1)
		self.thread1.start()

		# Start the periodic call in the GUI to check if the queue contains
		# anything
		self.periodicCall()

	def periodicCall(self):
		"""
		Check every 100 ms if there is something new in the queue.
		"""
		self.gui.processIncoming()
		if not self.running:
			# This is the brutal stop of the system. You may want to do
			# some cleanup before actually shutting it down.
			sys.exit(1)
		self.gui.after(100, self.periodicCall)

	def workerThread1(self):
		while self.running:
			(msg_type, data) = self.gui.queue.get(True)
			
			if msg_type == 'file':
				self._set_status('Reading links from file')
				for line in data.readlines():
					line = line.strip(' \r\n\t')
					if urlparse.urlparse(line)[0] == 'http':
						self.guiqueue.put((self.gui.table.add_row, [line], {}))
			elif msg_type == 'url':
				self._set_status('Getting %s' % (data[0]))
				self.gui.curr_row = data[1]

				try:
					try: get_with_retry(data[0])
					except Exception, msg:
						self.guiqueue.put((self.gui.statuswin.insert_text, [msg, 'error'], {}))
						self.guiqueue.put((self.gui.statusbar.clear, [], {}))
						continue
				finally:
					self.gui.curr_row = None
					self.gui.currently_getting -= 1
					if self.gui.currently_getting <= 0:
						self.guiqueue.put((self.gui.button_pane.set_state_idle, [], {}))
					
			elif msg_type == 'source-url':
				self._set_status('Getting links from %s' % data)
				try: links = get_links(data, plugins.url_filter)
				except Exception, msg:
					self.guiqueue.put((self.gui.statuswin.insert_text, [msg, 'error'], {}))
					self.guiqueue.put((self.gui.statusbar.clear, [], {}))
					continue
				for l in links:
					self.guiqueue.put((self.gui.table.add_row, [l], {}))
			elif msg_type == 'quit':
				break
			self.guiqueue.put((self.gui.save_status, [], {}))
			self.guiqueue.put((self.gui.stop_waiting, [], {}))
			self.guiqueue.put((self.gui.statusbar.clear, [], {}))

	def _set_status(self, msg):
		self.guiqueue.put((self.gui.statusbar.set, [msg], {}))
		
	def endApplication(self):
		self.running = False

	def run(self):
		self.gui.mainloop()

class StatusWindow(ScrolledText):
	"""Status window text area"""
	def __init__(self, master=None, **cnf):
		cnf['state'] = DISABLED
		cnf['height'] = 5
		cnf['bg'] = 'white'
		cnf['cursor'] = ''
		ScrolledText.__init__(self, master, **cnf)
		self.tag_config('error', foreground='red')
		self.tag_config('warning', foreground='green')
		self.tag_config('info', foreground='blue')
		self.bind('<Button-4>', lambda event : self.xview('scroll', -1, 'units'))
		self.bind('<Button-5>', lambda event : self.xview('scroll', 1, 'units'))

	def insert_text(self, str, style=None):
		self.configure(state=NORMAL)
		str = '%s@%s' % (time.ctime(time.time()), str)
		if style:
			self.insert(END, str, style)
		else:
			self.insert(END, str)
		self.configure(state=DISABLED)

class FontChooser(LabelFrame):
	"""Font selector widget"""
	def __init__(self, master, font, title):
		LabelFrame.__init__(self, master, label=title)

		b1 = ComboBox(self.frame)
		b2 = ComboBox(self.frame)
		b1.entry['width'] = 30
		b1.entry['disabledbackground'] = b1.entry['background']
		b1.entry['disabledforeground'] = b1.entry['foreground']
		b2.entry['width'] = 4
		b2.entry['disabledbackground'] = b2.entry['background']
		b2.entry['disabledforeground'] = b2.entry['foreground']
		b2.slistbox.listbox['height'] = 3

		curr_index = 0
		curr_family = font.cget('family')
		i = 0
		for fnt in tkFont.families():
			if fnt == curr_family:
				curr_index = i
			i += 1
			b1.insert(END, fnt)

		b1.pick(curr_index)

		curr_index = 0
		i = 0
		curr_size = font.cget('size')
		for sz in ['8', '9', '10', '11', '12', '14', '16', '18', '20', '22', '24']:
			if int(sz) == int(curr_size):
				curr_index = i
			b2.insert(END, sz)
			i += 1

		b2.pick(curr_index)

		curr_weight = font.cget('weight') # NORMAL, BOLD
		curr_slant = font.cget('slant') # ROMAN, ITALIC

		self.select_var = StringVar()
		if curr_weight == 'bold':
			self.select_var.set('bold')

		if curr_slant == 'italic':
			self.select_var.set(self.select_var.get() + ' italic')

		f = Button()['font']
		boldfont = tkFont.Font(font=f)
		italicfont = boldfont.copy()
		boldfont['slant'] = tkFont.ROMAN
		boldfont['weight'] = tkFont.BOLD
		italicfont['slant'] = tkFont.ITALIC
		italicfont['weight'] = tkFont.NORMAL
		select = Select(self.frame, allowzero=1, radio=0, label='')
		select.add('bold', text='B', font=boldfont, width=2)
		select.add('italic', text='I', font=italicfont, width=2)
		select.configure(variable=self.select_var)

		# TODO: add underline -- font underlining: false (0), true (1)
		# TODO: add overstrike -- font strikeout: false (0), true (1)

		self.font_combo = b1
		self.size_combo = b2
		self.style_select = select
		self.font = font
		b1.pack(side=LEFT, padx=4, pady=4)
		b2.pack(side=LEFT, padx=4, pady=4)
		select.pack(side=LEFT, padx=4, pady=4)

	def apply(self):
		self.font.configure(family=self.font_combo.entry.get())
		self.font.configure(size=self.size_combo.entry.get())
		if self.select_var.get().find('bold') != -1:
			self.font.configure(weight=tkFont.BOLD)
		else:
			self.font.configure(weight=tkFont.NORMAL)
		if self.select_var.get().find('italic') != -1:
			self.font.configure(slant=tkFont.ITALIC)
		else:
			self.font.configure(slant=tkFont.ROMAN)

class FontsDialog(tkSimpleDialog.Dialog):
	"""Class for choosing fonts"""
	def __init__(self, root, main_fnt, entry_fnt, **cnf):
		self.main_fnt = main_fnt
		self.entry_fnt = entry_fnt
		tkSimpleDialog.Dialog.__init__(self, root, **cnf)

	def body(self, master):
		self.main_font = FontChooser(master, self.main_fnt, 'Main font')
		self.main_font.pack()
		self.entry_font = FontChooser(master, self.entry_fnt, 'Entries font')
		self.entry_font.pack()
		return self.main_font.font_combo

	def apply(self):
		self.main_font.apply()
		self.entry_font.apply()

class AppGui(Tk):
	"""Our application main window"""
	def __init__(self, queue, endCommand, **cnf):
		Tk.__init__(self, **cnf)
		self.geometry('600x300')
		self.queue = Queue.Queue()
		self.guiqueue = queue
		self.end_cmd = endCommand
		self.status_filename = find_file('curr_getting.txt')
		self.conf = PersistentConfig()
		self.menubar = Menu(self)
		self.title('YapGUI')

		self.options_file = find_file('yapgui_options')
		if os.path.isfile(self.options_file):
			self.option_readfile(self.options_file)

		# Create named fonts, reading the current defaults
		# (thus if users use option database files, this honors their settings).
		mainFontDescr = Button()['font'] # same as Label, Checkbutton, Menu...
		entryFontDescr = Entry()['font'] # same as Text
		self.mainFont = tkFont.Font(font=mainFontDescr)
		self.entryFont = tkFont.Font(font=entryFontDescr)
		# Set the option database; be sure to set the more general options first.
		self.option_add('*Font', self.mainFont)
		self.option_add('*Entry*Font', self.entryFont)
		self.option_add('*Text*Font', self.entryFont)

		# create a pulldown menu, and add it to the menu bar
		filemenu = Menu(self.menubar, tearoff=0)
		self.filemenu = filemenu
		filemenu.add_command(label='Import from files', command=self.file_open_file, accelerator='Ctrl+O', underline=12)
		filemenu.add_command(label='Import URL', command=self.file_open_url, accelerator='Ctrl+U', underline=7)
		filemenu.add_command(label='Import source URL', command=self.file_open_url_src, accelerator='Ctrl+S', underline=8)
		filemenu.add_command(label='Export links', command=self.file_save_file, accelerator='Ctrl+X', underline=1)
		filemenu.add_separator()
		filemenu.add_command(label='Properties', command=self.file_properties, underline=0)
		filemenu.add_command(label='Fonts', command=self.file_main_fonts, underline=0)
		filemenu.add_separator()
		filemenu.add_command(label='Exit', command=self._quit, accelerator='Ctrl+Q', underline=1)
		self.menubar.add_cascade(label='File', menu=filemenu, underline=0)
		
		helpmenu = Menu(self.menubar, tearoff=0)
		helpmenu.add_command(label='About', command=self.help_about, underline=0)
		self.menubar.add_cascade(label='Help', menu=helpmenu, underline=0)
		self.bind_all('<Control-q>', lambda event: self._quit())
		self.bind_all('<Control-Q>', lambda event: self._quit())
		
		self.bind_all('<Control-o>', lambda event: self.file_open_file())
		self.bind_all('<Control-O>', lambda event: self.file_open_file())

		self.bind_all('<Control-u>', lambda event: self.file_open_url())
		self.bind_all('<Control-U>', lambda event: self.file_open_url())

		self.bind_all('<Control-s>', lambda event: self.file_open_url_src())
		self.bind_all('<Control-S>', lambda event: self.file_open_url_src())

		self.bind_all('<Control-x>', lambda event: self.file_save_file())
		self.bind_all('<Control-X>', lambda event: self.file_save_file())

		self.currently_getting = False
		self.button_pane = ButtonPane(self)

		# intercept window close events
		self.protocol('WM_DELETE_WINDOW', self._quit)

		pane = PanedWindow(self, orientation='vertical', panebd=2)
		p1 = pane.add('list', min=80, expand=0.5)
		p2 = pane.add('text', min=40)

		self.table = GridTable(p1)
		self.statuswin = StatusWindow(p2)
		self.table.pack(expand=1, fill=BOTH)
		self.statuswin.pack(expand=1, fill=BOTH)

		# display the menu
		self.config(menu=self.menubar)
		
		self.statusbar = StatusBar(self)
		self.curr_row = None

		self.button_pane.pack()
		pane.pack(expand=1, fill=BOTH)
		self.statusbar.pack(fill=X)
		self.load_status()

	def _write_option(self, f, option, val):
		opt_str = '%s : %s\n' % (option, val) 
		f.write(opt_str)

	def _get_font_description(self, font):
		family = font['family']
		size = font['size']
		if font['weight'] == tkFont.BOLD:
			weight = 'bold'
		else:
			weight = ''

		if font['slant'] == tkFont.ROMAN:
			slant = ''
		else:
			slant = 'italic'
		return '{%s} %s %s %s' % (family, size, weight, slant)

	def _save_gui_options(self):
		f = file(self.options_file, 'w')
		mainFontDescr = self._get_font_description(self.mainFont)
		entryFontDescr = self._get_font_description(self.entryFont)
		self._write_option(f, '*Font', mainFontDescr)
		self._write_option(f, '*Entry*Font', entryFontDescr)
		self._write_option(f, '*Text*Font', entryFontDescr)
		f.close()

	def _quit(self):
		need_to_quit = True
		
		if self.currently_getting > 0:
			if not tkMessageBox.askyesno('YapGUI', 'Downloads in progress.\nAre you sure you want to quit YapGUI?', parent=self):
				need_to_quit = False
		
		if need_to_quit:
			self.button_pane.on_stop()
			self.queue.put(('quit', 'now'))
			self.end_cmd()
			Tk.quit(self)

	def start_waiting(self):
		self.config(cursor='wait')

	def stop_waiting(self):
		self.config(cursor='')

	def file_open_file(self):
		filetypes = [('All', '*.*'), ('Text', '.txt')]
		files = tkFileDialog.askopenfiles(parent=self, title='Import links from files', filetypes=filetypes)
		if files:
			self.start_waiting()
		for f in files:
			self.queue.put(('file', f))

	def file_open_url(self):
		f = tkSimpleDialog.askstring('Enter URL to get', 'URL', parent=self)
		if f:
			self.table.add_row(f)

	def file_open_url_src(self):
		f = tkSimpleDialog.askstring('Enter URL to get links from', 'Source URL', parent=self)
		if f:
			self.start_waiting()
			self.queue.put(('source-url', f))

	def file_properties(self):
		PropertiesDialog(self)

	def file_main_fonts(self):
		FontsDialog(self, self.mainFont, self.entryFont, title='Select fonts')
		self._save_gui_options()

	def _save_links(self, f):
		for row in self.table.rows:
			if row.completed:
				f.write('# completed # ' + row.get_url())
			elif row.var.get(): # selected
				f.write(row.get_url())
			else: # not selected
				f.write('#' + row.get_url())
			f.write(config.eol)
		f.close()

	def save_status(self):
		f = file(self.status_filename, 'w')
		self._save_links(f)

	def load_status(self):
		if os.path.isfile(self.status_filename):
			f = file(self.status_filename, 'r')
			self.queue.put(('file', f))

	def file_save_file(self):
		filetypes = [('Text', '.txt'), ('All', '*.*')]
		f = tkFileDialog.asksaveasfile(parent=self, title='Export links to file', filetypes=filetypes, mode='a', defaultextension='.txt')
		if f:
			self._save_links(f)

	def help_about(self):
		tkMessageBox.showinfo('About YapGUI',
			'Yet Another Python Getter GUI\n' +
			'Download files from different sites:\n\t' + plugins.sites +
			'\n\nYapget %s\nYapGUI: %s\nWritten by Yan Burman' % (yapget_version, gui_version),
			parent=self)

	def processIncoming(self):
		"""Handle all the messages currently in the queue (if any)."""
		while self.guiqueue.qsize():
			try:
                		func, attr, kw = self.guiqueue.get_nowait()
				func(*attr, **kw)
			except Queue.Empty:
				pass

def main():
	"""Application main"""
	app = App()
	conf = app.gui.conf
	plugins.initialize()
	conf.read()
	config.initialize(conf['user'], conf['password'], conf['retries'], StatusReportWin(app),
		conf.getboolean('overwrite'), conf['dl_dir'], conf['timeout'])
	proxy = conf['explicit_proxy']
	if not proxy:
		proxy = None
	config.init_comm(conf['cookie_dir'], conf.getboolean('no_proxy'), proxy)
	app.run()
	return 0

if __name__ == "__main__":
	ret = 1
	try: ret = main()
	except:
		fd = file('bug_report.txt', 'a')
		fd.write(time.ctime(time.time()))
		fd.write(config.eol)
		fd.write('-------------------------------------------')
		fd.write(config.eol)
		fd.write('YapGUI version %s crash report' % gui_version)
		fd.write(config.eol)
		traceback.print_exc(file=fd)
		fd.write(config.eol)
		fd.write('-------------------------------------------')
		fd.write(config.eol)
		fd.close()
		sys.exit(1)
	sys.exit(ret)

